package XMLScene;

import java.awt.Point;
import Tracer.RTBitmap;
import Tracer.RTColor;
import Tracer.RTHitpointObjMap;
import Tracer.RTRay;
import Tracer.RTVector;

/**
 * Model class of sphere object
 * 
 * @author Forrest Lorraine Eagle
 */

public class RTSphere implements RTIObject {
	/**
	 * radius of sphere
	 */
	private double sRadius;
	/**
	 * location of sphere
	 */
	private RTVector sLocation;

	/**
	 * pole of sphere
	 */
	private RTVector sPole;
	/**
	 * equator of sphere
	 */
	private RTVector sEquator;
	/**
	 * surface of sphere
	 */
	private RTSurface sSurface = null;

	/**
	 * Constructor of RTSphere with location and raduis
	 * 
	 * @param location
	 * @param radius
	 */
	public RTSphere(final RTVector location, final double radius) {
		sLocation = new RTVector(location);
		sRadius = radius;
		// default surface is black pure color
		sSurface = new RTSurface(RTColor.BLACK);
		// default coordination for pole is (0,1,0)
		sPole = new RTVector(0, 1, 0);
		// default coordination for equator is (0,0,1)
		sEquator = new RTVector(0, 0, 1);
	}

	/**
	 * Setter of surface
	 * 
	 * @param surface
	 */
	public final void setSurface(final RTSurface surface) {
		sSurface = surface;
	}

	/**
	 * Setter method of pole vector
	 * 
	 * @param pole
	 */
	public final void setPole(final RTVector pole) {
		sPole = new RTVector(pole);
	}

	/**
	 * Setter of equator vector.
	 * 
	 * @param equator
	 */

	public final void setEquator(final RTVector equator) {
		sEquator = new RTVector(equator);
	}

	/**
	 * Getter method of surface
	 * 
	 * @return surface of sphere
	 * @see doc.RTSphere.javadoc
	 * @see XMLScene.RTIObject#getSurface()
	 */
	public final RTSurface getSurface() {
		return sSurface;
	}

	/**
	 * Getter method of location vector
	 * 
	 * @return location
	 */
	public final RTVector getLocation() {
		return sLocation;
	}

	/**
	 * Getter method of pole vector
	 * 
	 * @return pole of sphere
	 */
	public final RTVector getPole() {
		return sPole;
	}

	/**
	 * Getter method of equator vector
	 * 
	 * @return equator of sphere
	 */
	public final RTVector getEquator() {
		return sEquator;
	}

	/**
	 * Getter method of radius of the sphere
	 * 
	 * @return
	 */
	public double getRadius() {
		return sRadius;
	}

	/**
	 * Implementing method from super class RTIObject, used for detecting if a
	 * ray hits any object.
	 */
	public RTHitpointObjMap objectHitDetect(RTRay ray,
			RTHitpointObjMap hitPointObjSingleMap) {
		// OC is the vector from camera to the center of sphere
		RTVector OC = RTVector.sumVectors(sLocation, RTVector.reverseVector(ray
				.getStartPoint()));
		double squareOC = RTVector.dotProductVectors(OC, OC);
		// distanceCA is the distance between origin of the ray and the
		// point on the ray closest to the centre of sphere
		double distanceCA = RTVector.dotProductVectors(OC, ray.getDirection());
		// square of radius
		double squareRadius = sRadius * sRadius;
		// squareHC is the distance from the hitpoint to the point on the
		// ray closest to the centre of sphere
		double squareHC = squareRadius - squareOC + distanceCA * distanceCA;

		if (squareOC <= squareRadius)// inside the sphere, must hit, =
		// represent the camera is on the
		// surface of sphere
		{
			// distanceOH is the distance between origin of the ray and hitpoint
			double distanceOH = distanceCA + Math.sqrt(squareHC);

			if (distanceOH > 0) {
				hitPointObjSingleMap.setHitPoint(RTVector.sumVectors(ray
						.getStartPoint(), RTVector.multipleVectors(RTVector
						.normaliseVectors(ray.getDirection()), distanceOH)));
			}

			// normal vector on the hitpoint, not used yet
			hitPointObjSingleMap.setHitNormal(RTVector
					.normaliseVectors(RTVector.multipleVectors(RTVector
							.sumVectors(hitPointObjSingleMap.getHitPoint(),
									RTVector.reverseVector(sLocation)), -1)));
			hitPointObjSingleMap.setHitObject(this);
		} else // outside the sphere, may hit or not
		{
			// ray strike the sphere: angle between OC and CA smaller than
			// 90 && the distance
			// between center to ray is smaller than radius
			if (distanceCA >= 0 && squareHC >= 0) {
				// do not consider the hitpoint far from ray origin
				double distanceOH = distanceCA - Math.sqrt(squareHC);
				if (distanceOH > 0) {
					hitPointObjSingleMap
							.setHitPoint(RTVector.sumVectors(ray
									.getStartPoint(), RTVector.multipleVectors(
									RTVector.normaliseVectors(ray
											.getDirection()), distanceOH)));
				}
				hitPointObjSingleMap.setHitObject(this);
				// normal vector on the hitpoint, not used yet
				hitPointObjSingleMap.setHitNormal(RTVector
						.normaliseVectors(RTVector.sumVectors(RTVector
								.reverseVector(sLocation), hitPointObjSingleMap
								.getHitPoint())));
			} else {
				// ray not strike the sphere
				return null;
			}
		}

		return hitPointObjSingleMap;
	}

	/**
	 * Implements method from interface RTIObject.
	 * 
	 * @return the original color of a pixel before shading and reflection
	 */
	public RTColor calOriginColor(RTHitpointObjMap validHitPointObjMap) {
		// Valid hit point exist
		RTVector hitPointNormal = validHitPointObjMap.getHitNormal();

		if (sSurface.getPigment().getColor() != null) {
			return sSurface.getPigment().getColor();
		} else {

			RTBitmap objectBitmap = sSurface.getPigment().getBitmap();

			// Calculate the coordination of corresponding Height on the bitmap
			// the angle between pole and reverse vector of hitPointNormal
			double dotMultSpRn = RTVector.dotProductVectors(RTVector
					.reverseVector(hitPointNormal), sPole);
			if (dotMultSpRn > 1)
				dotMultSpRn = 1;
			else if (dotMultSpRn < -1)
				dotMultSpRn = -1;
			double anglePrN = Math.acos(dotMultSpRn);
			double vHeightBitmap = anglePrN / Math.PI;

			// calculate the coordination of corresponding Height on the
			// bitmap
			double uWidthBitmap;
			if (vHeightBitmap == 0 || vHeightBitmap == 1) {
				uWidthBitmap = 0;
			} else {
				double dotSeRnDivF = RTVector.dotProductVectors(sEquator,
						hitPointNormal)
						/ Math.sin(anglePrN);
				if (dotSeRnDivF > 1)
					dotSeRnDivF = 1;
				else if (dotSeRnDivF < -1)
					dotSeRnDivF = -1;

				double angleENP = Math.acos(dotSeRnDivF);
				double sita = angleENP / (2 * Math.PI);

				double paramEPN = RTVector.dotProductVectors(RTVector
						.crossProductVectors(sEquator, sPole), hitPointNormal);
				// determine the width scale
				if (paramEPN > 0)
					uWidthBitmap = sita;
				else
					uWidthBitmap = 1 - sita;
			}

			// convert the coordination from lower left-hand to upper
			// left-hand zero
			vHeightBitmap = 1.0 - vHeightBitmap;
			// get the color of corresponding point of bitmap
			Point hitPointonBitmap = new Point();
			hitPointonBitmap.setLocation(uWidthBitmap
					* (objectBitmap.width() - 1), vHeightBitmap
					* (objectBitmap.height() - 1));

			return objectBitmap.getColorAtPoint(hitPointonBitmap);
		}
	}
}
